home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tricks of the Mac Game Programming Gurus
/
TricksOfTheMacGameProgrammingGurus.iso
/
Book Chapters
/
10 - Networking
/
NovelNetwar
/
gameplay.c
< prev
next >
Wrap
Text File
|
1995-05-12
|
29KB
|
1,310 lines
// This module implements all the actual gameplay related activity.
#include "NovelNetwar.h"
#include <QDOffscreen.h>
#include <math.h>
#include <Sound.h>
#define LOCALPLAYER 0
#define LEFTARROW 0x1C
#define RIGHTARROW 0x1D
#define UPARROW 0x1E
#define LEFTARROWKEYMAPMASK 0x0008
#define RIGHTARROWKEYMAPMASK 0x0010
#define UPARROWKEYMAPMASK 0x0040
#define SPACEKEYMAPMASK 0x0200
#define NUMANGLES (short) (72)
#define REALANGLEDELTA (double) (360.0 / NUMANGLES)
#define MINANGLEINDEX (short) 0
#define MAXANGLEINDEX (short) (MINANGLEINDEX + NUMANGLES)
#define NUMSTARS 50
#define SUNRADIUS 15
#define SIDELENGTH 10
#define MAXSPEED 7.5
#define THRUST 0.25
#define GRAVITY -300.0
//#define GRAVITY 0.0
#define PHOTONLIFETIME 100
#define PHOTONFIREDELAY 20L
#define MAXNUMCHANNELS 4
#define EXPLOSIONTTL 40
enum { IDLE, FINDINGOPPONENTS, PLAYING, EXPLODING };
// Lots of private variables-- the names make their purpose fairly clear (I hope).
// For smoother action, make our local variables floating point
static double myXCoord,myYCoord,myVX,myVY;
static double myPhotonXCoords[MAXNUMPHOTONS],myPhotonYCoords[MAXNUMPHOTONS];
static double myPhotonVXs[MAXNUMPHOTONS],myPhotonVYs[MAXNUMPHOTONS];
// For speed, make the coordinates of other players integer
static short playerXCoord[MAXNUMPLAYERS],playerYCoord[MAXNUMPLAYERS];
static short playerLastXCoord[MAXNUMPLAYERS],playerLastYCoord[MAXNUMPLAYERS];
static short playerAngleIndex[MAXNUMPLAYERS],playerLastAngleIndex[MAXNUMPLAYERS];
static short playerTempCurrentX[MAXNUMPLAYERS],playerTempCurrentY[MAXNUMPLAYERS],playerTempCurrentAngle[MAXNUMPLAYERS];
static short photonLastXCoord[MAXNUMPLAYERS][MAXNUMPHOTONS],photonLastYCoord[MAXNUMPLAYERS][MAXNUMPHOTONS];
static short photonXCoord[MAXNUMPLAYERS][MAXNUMPHOTONS],photonYCoord[MAXNUMPLAYERS][MAXNUMPHOTONS];
static short photonTimeToLive[MAXNUMPLAYERS][MAXNUMPHOTONS],photonLastTimeToLive[MAXNUMPLAYERS][MAXNUMPHOTONS];
static short playerStatus[MAXNUMPLAYERS];
static short photonIndex;
static unsigned long photonLastTickCount;
static short xVertexTable[NUMANGLES+1][3],yVertexTable[NUMANGLES+1][3];
static double vxTable[NUMANGLES+1],vyTable[NUMANGLES+1];
static double photonVXTable[NUMANGLES+1],photonVYTable[NUMANGLES+1];
static short midx,midy;
static CWindowPtr gameWPtr;
static CWindowRecord gameWRecord;
static GWorldPtr gameMasterHiddenGWorld,gameTempHiddenGWorld;
static Rect gameRect,sunRect;
static int gameNumPlayers;
static AddrBlock gamePlayerAddresses[MAXNUMPLAYERS];
static char sendBroadcastUpdate,dataBroadcastIsComplete,startExplosionSND;
static Handle gameMyPhotonSNDHandle,gameEnemyPhotonSNDHandle,gameExplosionSNDHandle;
static SndChannelPtr gameSndChannelPtr[MAXNUMCHANNELS],gameExplosionSndChannelPtr;
static int gameExplosionTimeToLive;
static void DrawSunAndStars(void);
static void ErasePhotons(void);
static void DrawPhotons(void);
static void BroadcastLocalPlayerData(void);
static void EraseOffscreenShip(int whichShip);
static void DrawOffscreenShip(int whichShip);
static void ShowShip(int whichShip);
static void PlayGameSound(Handle theSNDHandle);
// Initialize all the private variables
void gameplayInit(void)
{
int i,j;
// precalculate all the trig stuff
for (i=0;i<=NUMANGLES;i++)
{
xVertexTable[i][0] = (short) (SIDELENGTH * cosd(pi * (i*REALANGLEDELTA) / 180.0));
xVertexTable[i][1] = (short) (SIDELENGTH * cosd(pi * (i*REALANGLEDELTA + 135.0) / 180.0));
xVertexTable[i][2] = (short) (SIDELENGTH * cosd(pi * (i*REALANGLEDELTA + 225.0) / 180.0));
yVertexTable[i][0] = (short) (SIDELENGTH * sind(pi * (i*REALANGLEDELTA) / 180.0));
yVertexTable[i][1] = (short) (SIDELENGTH * sind(pi * (i*REALANGLEDELTA + 135.0) / 180.0));
yVertexTable[i][2] = (short) (SIDELENGTH * sind(pi * (i*REALANGLEDELTA + 225.0) / 180.0));
vxTable[i] = THRUST * cosd(pi * (i*REALANGLEDELTA) / 180.0);
vyTable[i] = THRUST * sind(pi * (i*REALANGLEDELTA) / 180.0);
photonVXTable[i] = MAXSPEED * cosd(pi * (i*REALANGLEDELTA) / 180.0);
photonVYTable[i] = MAXSPEED * sind(pi * (i*REALANGLEDELTA) / 180.0);
}
// initialize all the state variables for other players
for (i=0;i<MAXNUMPLAYERS;i++)
{
playerXCoord[i] = 0;
playerYCoord[i] = 0;
playerAngleIndex[i] = 0;
playerLastXCoord[i] = 0;
playerLastYCoord[i] = 0;
playerLastAngleIndex[i] = 0;
playerStatus[i] = IDLE;
for (j=0;j<MAXNUMPHOTONS;j++)
{
photonXCoord[i][j] = 0;
photonYCoord[i][j] = 0;
photonTimeToLive[i][j] = 0;
myPhotonXCoords[j] = 0;
myPhotonYCoords[j] = 0;
myPhotonVXs[j] = 0;
myPhotonVYs[j] = 0;
}
}
photonIndex = 0;
gameWPtr = nil;
gameMasterHiddenGWorld = nil;
gameTempHiddenGWorld = nil;
gameExplosionTimeToLive = 0;
gameNumPlayers = 1;
playerStatus[LOCALPLAYER] = PLAYING;
sendBroadcastUpdate = true;
dataBroadcastIsComplete = true;
startExplosionSND = false;
gameMyPhotonSNDHandle = nil;
gameEnemyPhotonSNDHandle = nil;
gameExplosionSNDHandle = nil;
gameExplosionSndChannelPtr = nil;
for (i=0;i<MAXNUMCHANNELS;i++)
{
gameSndChannelPtr[i] = nil;
}
}
// Attempt to startup game module-- open windows, allocate sound channels, etc.
void gameplayStartup(void)
{
GDHandle currDevice;
CGrafPtr currPort;
OSErr errCode;
PixMapHandle pixBase;
gameWPtr = (CWindowPtr) GetNewCWindow(GAMEWIND,&gameWRecord,(WindowPtr) -1L);
if (gameWPtr == nil)
{
FatalError("Can't allocate memory for game window!");
}
SetPort((GrafPtr) gameWPtr);
CenterWindow((WindowPtr) gameWPtr);
ShowWindow((WindowPtr) gameWPtr);
gameRect = gameWPtr->portRect;
BackPat(&(qd.black));
EraseRect(&gameRect);
GetGWorld(&currPort,&currDevice);
errCode = NewGWorld(&gameMasterHiddenGWorld,0,&gameRect,0L,0L,0);
if (errCode != noErr)
{
FatalError("Can't allocate memory for master offscreen GWorld!");
}
errCode = NewGWorld(&gameTempHiddenGWorld,0,&gameRect,0L,0L,0);
if (errCode != noErr)
{
FatalError("Can't allocate memory for temporary offscreen GWorld!");
}
midx = gameRect.left + (gameRect.right - gameRect.left) / 2;
midy = gameRect.top + (gameRect.bottom - gameRect.top) / 2;
sunRect.left = midx - SUNRADIUS;
sunRect.right = midx + SUNRADIUS;
sunRect.top = midy - SUNRADIUS;
sunRect.bottom = midy + SUNRADIUS;
myXCoord = gameRect.left + (gameRect.right - gameRect.left) / 4;
myYCoord = gameRect.top + (gameRect.bottom - gameRect.top) / 4;
myVX = 0;
myVY = 0;
playerXCoord[LOCALPLAYER] = myXCoord;
playerYCoord[LOCALPLAYER] = myYCoord;
playerAngleIndex[LOCALPLAYER] = MINANGLEINDEX;
playerLastXCoord[LOCALPLAYER] = playerXCoord[LOCALPLAYER];
playerLastYCoord[LOCALPLAYER] = playerYCoord[LOCALPLAYER];
playerLastAngleIndex[LOCALPLAYER] = playerAngleIndex[LOCALPLAYER];
ObscureCursor();
DrawSunAndStars();
// offscreen bitmaps make for smooth animation!
pixBase = GetGWorldPixMap(gameMasterHiddenGWorld);
if (pixBase)
{
LockPixels(pixBase);
CopyBits(&(((GrafPtr) gameWPtr)->portBits),(BitMapPtr) *pixBase,&gameRect,&gameRect,srcCopy,nil);
UnlockPixels(pixBase);
}
pixBase = GetGWorldPixMap(gameTempHiddenGWorld);
if (pixBase != nil)
{
LockPixels(pixBase);
CopyBits(&(((GrafPtr) gameWPtr)->portBits),(BitMapPtr) *pixBase,&gameRect,&gameRect,srcCopy,nil);
UnlockPixels(pixBase);
}
errCode = SndNewChannel(&gameExplosionSndChannelPtr,0,0L,0L);
if (errCode != noErr || gameExplosionSndChannelPtr == nil)
{
gameExplosionSndChannelPtr = nil;
FatalError("Can't open a sound channel!");
}
gameMyPhotonSNDHandle = GetNamedResource('snd ',"\pMy Photon");
gameEnemyPhotonSNDHandle = GetNamedResource('snd ',"\pEnemy Photon");
gameExplosionSNDHandle = GetNamedResource('snd ',"\pExplosion");
}
// Shutdown handler-- close windows, release sound channels, etc.
void gameplayShutdown(void)
{
int i;
if (gameMasterHiddenGWorld)
{
DisposeGWorld(gameMasterHiddenGWorld);
gameMasterHiddenGWorld = nil;
}
if (gameTempHiddenGWorld)
{
DisposeGWorld(gameTempHiddenGWorld);
gameTempHiddenGWorld = nil;
}
if (gameWPtr)
{
DisposeWindow((WindowPtr) gameWPtr);
gameWPtr = nil;
}
if (gameMyPhotonSNDHandle)
{
ReleaseResource(gameMyPhotonSNDHandle);
gameMyPhotonSNDHandle = 0L;
}
if (gameEnemyPhotonSNDHandle)
{
ReleaseResource(gameEnemyPhotonSNDHandle);
gameEnemyPhotonSNDHandle = 0L;
}
if (gameExplosionSNDHandle)
{
ReleaseResource(gameExplosionSNDHandle);
gameExplosionSNDHandle = 0L;
}
if (gameExplosionSndChannelPtr)
{
SndDisposeChannel(gameExplosionSndChannelPtr,true);
gameExplosionSndChannelPtr = 0L;
}
for (i=0;i<MAXNUMCHANNELS;i++)
{
if (gameSndChannelPtr[i])
{
SndDisposeChannel(gameSndChannelPtr[i],true);
gameSndChannelPtr[i] = 0L;
}
}
}
// Nothing much to do for window activation right now....
void gameplayActivateWindow(WindowPtr theWPtr,char adFlag)
{
}
// Nothing much to do for mouseclicks in the window either....
void gameplayDoContent(WindowPtr theWPtr,EventRecord *theEvent)
{
GrafPtr oldPort;
if (gameWPtr != nil && theWPtr == (WindowPtr) gameWPtr)
{
GetPort(&oldPort);
SetPort((GrafPtr) gameWPtr);
GlobalToLocal(&theEvent->where);
SetPort(oldPort);
}
}
// The windows.c code calls this to check to see if an event applies to one of our windows.
OSErr gameplayIsWindow(WindowPtr theWPtr)
{
if (gameWPtr != nil && theWPtr == (WindowPtr) gameWPtr)
{
return(noErr);
}
else
{
return(BADWINDOW);
}
}
// Update the on-screen display by copying the image from the offscreen bitmap.
void gameplayUpdateWindow(WindowPtr theWPtr)
{
GrafPtr oldPort;
PixMapHandle pixBase;
if (gameWPtr != nil && theWPtr == (WindowPtr) gameWPtr)
{
GetPort(&oldPort);
SetPort((GrafPtr) gameWPtr);
BeginUpdate((WindowPtr) gameWPtr);
pixBase = GetGWorldPixMap(gameMasterHiddenGWorld);
if (pixBase)
{
LockPixels(pixBase);
CopyBits((BitMapPtr) *pixBase,&(((GrafPtr) gameWPtr)->portBits),&gameRect,&gameRect,srcCopy,nil);
UnlockPixels(pixBase);
}
EndUpdate((WindowPtr) gameWPtr);
SetPort(oldPort);
}
}
// Let the user drag the window around.
void gameplayDragWindow(WindowPtr theWPtr,EventRecord *theEvent)
{
Rect tempRect;
if (gameWPtr != nil && theWPtr == (WindowPtr) gameWPtr)
{
getGrayRgnRect(&tempRect);
DragWindow((WindowPtr) gameWPtr,theEvent->where,&tempRect);
}
}
// Draw the sun and the stars so they can be copied to the offscreen bitmap.
static void DrawSunAndStars(void)
{
int i;
short tempX,tempY;
SetPort((GrafPtr) gameWPtr);
BackPat(&(qd.black));
EraseRect(&gameRect);
PenNormal();
FillOval(&sunRect,&(qd.white));
GetDateTime((unsigned long *) &(qd.randSeed));
PenPat(&(qd.white));
for (i=0;i<NUMSTARS;i++)
{
tempX = gameRect.left + ((Random() & 0x7FFF) % (gameRect.right - gameRect.left));
tempY = gameRect.top + ((Random() & 0x7FFF) % (gameRect.bottom - gameRect.top));
MoveTo(tempX,tempY);
Line(0,0);
}
}
// Play a sound if a sound channel has been allocated and one is available.
static void PlayGameSound(Handle theSNDHandle)
{
int i,errCode;
SCStatus theStatus;
if (theSNDHandle == nil)
{
goto EXITPOINT;
}
for (i=0;i < MAXNUMCHANNELS;i++)
{
if (gameSndChannelPtr[i] == 0L)
{
errCode = SndNewChannel(&(gameSndChannelPtr[i]),0,0L,0L);
if (errCode != noErr)
{
gameSndChannelPtr[i] = 0L;
}
}
if (gameSndChannelPtr[i])
{
errCode = SndChannelStatus(gameSndChannelPtr[i],sizeof(theStatus),&theStatus);
if (errCode == noErr && theStatus.scChannelBusy == false)
{
errCode = SndPlay(gameSndChannelPtr[i],(SndListHandle) theSNDHandle,true);
goto EXITPOINT;
}
}
}
EXITPOINT:
return;
}
// Erase the image of a ship in the offscreen bitmap.
static void EraseOffscreenShip(int whichShip)
{
short lastX,lastY;
Rect updateRect;
PixMapHandle masterPixBase,tempPixBase;
lastX = (short) playerLastXCoord[whichShip];
lastY = (short) playerLastYCoord[whichShip];
updateRect.left = lastX - SIDELENGTH - 1;
updateRect.right = lastX + SIDELENGTH + 1;
updateRect.top = lastY - SIDELENGTH - 1;
updateRect.bottom = lastY + SIDELENGTH + 1;
SectRect(&updateRect,&gameRect,&updateRect);
// Copy the "blank" image (sun and stars only) over top of the players location
masterPixBase = GetGWorldPixMap(gameMasterHiddenGWorld);
tempPixBase = GetGWorldPixMap(gameTempHiddenGWorld);
if (masterPixBase != nil && tempPixBase != nil)
{
LockPixels(masterPixBase);
LockPixels(tempPixBase);
CopyBits((BitMapPtr) *masterPixBase,(BitMapPtr) *tempPixBase,&updateRect,&updateRect,srcCopy,nil);
UnlockPixels(masterPixBase);
UnlockPixels(tempPixBase);
}
}
// Draw a spaceship offscreen.
static void DrawOffscreenShip(int whichShip)
{
short centerX,centerY,angle,x1,y1,x2,y2,x3,y3;
PixMapHandle tempPixBase;
centerX = (short) playerXCoord[whichShip];
centerY = (short) playerYCoord[whichShip];
angle = (short) playerAngleIndex[whichShip];
x1 = centerX + xVertexTable[playerAngleIndex[whichShip]][0];
x2 = centerX + xVertexTable[playerAngleIndex[whichShip]][1];
x3 = centerX + xVertexTable[playerAngleIndex[whichShip]][2];
y1 = centerY + yVertexTable[playerAngleIndex[whichShip]][0];
y2 = centerY + yVertexTable[playerAngleIndex[whichShip]][1];
y3 = centerY + yVertexTable[playerAngleIndex[whichShip]][2];
SetPort((GrafPtr) gameTempHiddenGWorld);
PenNormal();
PenPat(&(qd.white));
MoveTo(x1,y1);
LineTo(x2,y2);
LineTo(centerX,centerY);
LineTo(x3,y3);
LineTo(x1,y1);
// The local player looks a little different
if (whichShip == LOCALPLAYER)
{
LineTo(centerX,centerY);
}
// since broadcast data arrives asynchronously, the location of the player could change while we're in the middle of drawing him,
// so we'd better keep track of where we think he is now
playerTempCurrentX[whichShip] = centerX;
playerTempCurrentY[whichShip] = centerY;
playerTempCurrentAngle[whichShip] = angle;
}
// Copy an offscreen ship onscreen where we can see it.
static void ShowShip(int whichShip)
{
short centerX,centerY,angle,lastX,lastY;
Rect updateRect;
PixMapHandle tempPixBase;
// remember that broadcast data arrives asynchronously, so use some dependable variables that don't change unexpectedly....
centerX = (short) playerTempCurrentX[whichShip];
centerY = (short) playerTempCurrentY[whichShip];
angle = playerTempCurrentAngle[whichShip];
lastX = (short) playerLastXCoord[whichShip];
lastY = (short) playerLastYCoord[whichShip];
if (centerX < lastX)
{
updateRect.left = centerX - SIDELENGTH - 1;
updateRect.right = lastX + SIDELENGTH + 1;
}
else
{
updateRect.left = lastX - SIDELENGTH - 1;
updateRect.right = centerX + SIDELENGTH + 1;
}
if (centerY < lastY)
{
updateRect.top = centerY - SIDELENGTH - 1;
updateRect.bottom = lastY + SIDELENGTH + 1;
}
else
{
updateRect.top = lastY - SIDELENGTH - 1;
updateRect.bottom = centerY + SIDELENGTH + 1;
}
SectRect(&updateRect,&gameRect,&updateRect);
tempPixBase = GetGWorldPixMap(gameTempHiddenGWorld);
if (tempPixBase != nil)
{
LockPixels(tempPixBase);
CopyBits((BitMapPtr) *tempPixBase,&(((GrafPtr) gameWPtr)->portBits),&updateRect,&updateRect,srcCopy,nil);
UnlockPixels(tempPixBase);
}
// if our position has changed since the last redraw, we need to broadcast our new position
if (whichShip == LOCALPLAYER && (playerLastXCoord[whichShip] != centerX || playerLastYCoord[whichShip] != centerY))
{
sendBroadcastUpdate = true;
}
playerLastXCoord[whichShip] = centerX;
playerLastYCoord[whichShip] = centerY;
playerLastAngleIndex[whichShip] = angle;
}
// XOR the photons to make them invisible again
static void ErasePhotons(void)
{
int i,j;
short x,y;
SetPort((GrafPtr) gameWPtr);
PenNormal();
PenMode(patXor);
for (i=0;i<gameNumPlayers;i++)
{
for (j=0;j<MAXNUMPHOTONS;j++)
{
if (photonTimeToLive[i][j] > 0)
{
x = photonLastXCoord[i][j];
y = photonLastYCoord[i][j];
if (x >= gameRect.left && x < gameRect.right &&
y >= gameRect.top && y < gameRect.bottom)
{
MoveTo(x,y);
Line(0,0);
}
}
}
}
}
// XOR the photons to make them visible
static void DrawPhotons(void)
{
int i,j;
short x,y,midx,midy;
Rect hitRect;
SetPort((GrafPtr) gameWPtr);
PenNormal();
PenMode(patXor);
// while we're drawing other players' photons, check to see if we've been hit
hitRect.left = playerTempCurrentX[LOCALPLAYER] - (2*SIDELENGTH)/3;
hitRect.right = playerTempCurrentX[LOCALPLAYER] + (2*SIDELENGTH)/3;
hitRect.top = playerTempCurrentY[LOCALPLAYER] - (2*SIDELENGTH)/3;
hitRect.bottom = playerTempCurrentY[LOCALPLAYER] + (2*SIDELENGTH)/3;
for (i=0;i<gameNumPlayers;i++)
{
for (j=0;j<MAXNUMPHOTONS;j++)
{
if (photonTimeToLive[i][j] > 0)
{
x = photonXCoord[i][j];
y = photonYCoord[i][j];
if (x >= gameRect.left && x < gameRect.right &&
y >= gameRect.top && y < gameRect.bottom)
{
MoveTo(x,y);
Line(0,0);
}
if (playerStatus[LOCALPLAYER] == PLAYING)
{
// have we been hit?
if (x >= hitRect.left && x <= hitRect.right && y >= hitRect.top && y <= hitRect.bottom)
{
playerStatus[LOCALPLAYER] = EXPLODING;
}
// check halfway between current location and previous location, too
else
{
midx = (x + photonLastXCoord[i][j]) / 2;
midy = (y + photonLastYCoord[i][j]) / 2;
if (midx >= hitRect.left && midx <= hitRect.right && midy >= hitRect.top && midy <= hitRect.bottom)
{
playerStatus[LOCALPLAYER] = EXPLODING;
}
}
if (playerStatus[LOCALPLAYER] == EXPLODING)
{
gameExplosionTimeToLive = EXPLOSIONTTL;
startExplosionSND = true;
}
}
photonLastXCoord[i][j] = x;
photonLastYCoord[i][j] = y;
}
if (i != LOCALPLAYER && photonLastTimeToLive[i][j] < photonTimeToLive[i][j])
{
PlayGameSound(gameEnemyPhotonSNDHandle);
}
photonLastTimeToLive[i][j] = photonTimeToLive[i][j];
}
}
}
// This routine gets called when a broadcast packet has been sent by the serialSendData() or appleTalkingSendData() routines
void gameplayHandleBroadcastCompletion(void)
{
dataBroadcastIsComplete = true;
}
// Attempt to broadcast a packet containing our current position, angle, photon positions....
static void BroadcastLocalPlayerData(void)
{
int i;
GameData theBroadcastData;
theBroadcastData.x = playerXCoord[LOCALPLAYER];
theBroadcastData.y = playerYCoord[LOCALPLAYER];
theBroadcastData.angleIndex = playerAngleIndex[LOCALPLAYER];
for (i=0;i<MAXNUMPHOTONS;i++)
{
theBroadcastData.photonsX[i] = photonXCoord[LOCALPLAYER][i];
theBroadcastData.photonsY[i] = photonYCoord[LOCALPLAYER][i];
theBroadcastData.photonsTimeToLive[i] = photonTimeToLive[LOCALPLAYER][i];
}
theBroadcastData.playerStatus = playerStatus[LOCALPLAYER];
if (connectionMethod == APPLETALK)
{
appleTalkingSendData((Ptr) &theBroadcastData,sizeof(GameData));
}
else if (connectionMethod == SERIAL)
{
serialSendData((Ptr) &theBroadcastData,sizeof(GameData));
}
}
// This routine handles incoming broadcast data from other players.
void gameReceiveBroadcastData(Ptr theBroadcastData,long theBroadcastDataLength)
{
int i,j;
char found;
GameData *thePacket;
// if we're AppleTalked, then we have to deal with data from many other players
if (connectionMethod == APPLETALK)
{
found = false;
for (i=1;i<gameNumPlayers;i++)
{
// Find out which player it came from
if (gamePlayerAddresses[i].aNode == (*((DDPDataPacket *) theBroadcastData)).srcNodeID && gamePlayerAddresses[i].aSocket == (*((DDPDataPacket *) theBroadcastData)).srcSocketNumber)
{
thePacket = (GameData *) &((*((DDPDataPacket *) theBroadcastData)).packetData);
playerXCoord[i] = (*thePacket).x;
playerYCoord[i] = (*thePacket).y;
playerAngleIndex[i] = (*thePacket).angleIndex;
for (j=0;j<MAXNUMPHOTONS;j++)
{
photonXCoord[i][j] = (*thePacket).photonsX[j];
photonYCoord[i][j] = (*thePacket).photonsY[j];
photonTimeToLive[i][j] = (*thePacket).photonsTimeToLive[j];
}
if (playerStatus[i] != EXPLODING && (*thePacket).playerStatus == EXPLODING)
{
startExplosionSND = true;
}
playerStatus[i] = (*thePacket).playerStatus;
found = true;
break;
}
}
// if it's someone we don't, then add them to our list of known players
if (found == false && gameNumPlayers < MAXNUMPLAYERS)
{
gamePlayerAddresses[gameNumPlayers].aNode = (*((DDPDataPacket *) theBroadcastData)).srcNodeID;
gamePlayerAddresses[gameNumPlayers].aSocket = (*((DDPDataPacket *) theBroadcastData)).srcSocketNumber;
thePacket = (GameData *) &((*((DDPDataPacket *) theBroadcastData)).packetData);
playerXCoord[gameNumPlayers] = (*thePacket).x;
playerYCoord[gameNumPlayers] = (*thePacket).y;
playerAngleIndex[gameNumPlayers] = (*thePacket).angleIndex;
for (j=0;j<MAXNUMPHOTONS;j++)
{
photonXCoord[gameNumPlayers][j] = (*thePacket).photonsX[j];
photonYCoord[gameNumPlayers][j] = (*thePacket).photonsY[j];
photonTimeToLive[gameNumPlayers][j] = (*thePacket).photonsTimeToLive[j];
photonLastTimeToLive[gameNumPlayers][j] = 0;
}
playerStatus[gameNumPlayers] = (*thePacket).playerStatus;
playerLastXCoord[gameNumPlayers] = playerXCoord[gameNumPlayers];
playerLastYCoord[gameNumPlayers] = playerYCoord[gameNumPlayers];
playerLastAngleIndex[gameNumPlayers] = playerAngleIndex[gameNumPlayers];
gameNumPlayers++;
}
}
// if we're connected via serial cable, then there can be only one other player
else if (connectionMethod == SERIAL)
{
gameNumPlayers = 2;
thePacket = (GameData *) theBroadcastData;
playerXCoord[1] = (*thePacket).x;
playerYCoord[1] = (*thePacket).y;
playerAngleIndex[1] = (*thePacket).angleIndex;
for (j=0;j<MAXNUMPHOTONS;j++)
{
photonXCoord[1][j] = (*thePacket).photonsX[j];
photonYCoord[1][j] = (*thePacket).photonsY[j];
photonTimeToLive[1][j] = (*thePacket).photonsTimeToLive[j];
}
if (playerStatus[1] != EXPLODING && (*thePacket).playerStatus == EXPLODING)
{
startExplosionSND = true;
}
playerStatus[1] = (*thePacket).playerStatus;
}
}
// This routine does most of the work of updating our position and figuring out when screen redraws should occur.
void gameplayIdle(void)
{
KeyMap theKeyMap;
int i,currentNumPlayers;
short x,y,dx,dy;
SCStatus theStatus;
OSErr errCode;
double ax,ay,rSquared,r,dt;
static unsigned long thisTicks,lastTicks = 0,deltaTicks;
static unsigned long totalTicks = 0,numTimes = 0;
// try to figure out how much time has elapsed since the last time we did this
thisTicks = TickCount();
if (lastTicks != 0)
{
deltaTicks = thisTicks - lastTicks;
}
lastTicks = thisTicks;
totalTicks += deltaTicks;
numTimes++; // this isn't necessary, but I used it to help generate some stats about the speed of the code
currentNumPlayers = gameNumPlayers;
for (i=0;i<currentNumPlayers;i++)
{
EraseOffscreenShip(i);
}
// update our position no more than 60 times per second (be nice to the slow machines we're playing against)
if (deltaTicks > 0)
{
dt = deltaTicks;
// don't you wish you'd paid attention in highschool when you did Pythagorean Theorem and Newton's Law of Gravitation?
rSquared = (myXCoord - midx)*(myXCoord - midx) + (myYCoord - midy)*(myYCoord - midy);
r = sqrtd(rSquared);
if (fabsd(r) > SUNRADIUS)
{
ax = ((myXCoord - midx) / r) * (GRAVITY / rSquared);
ay = ((myYCoord - midy) / r) * (GRAVITY / rSquared);
}
else
{
ax = 0.0;
ay = 0.0;
}
myVX += ax*dt;
myVY += ay*dt;
// If we've been hit, then bounce us around a bit
if (playerStatus[LOCALPLAYER] == EXPLODING)
{
if (gameExplosionTimeToLive > 0)
{
gameExplosionTimeToLive--;
}
else
{
playerStatus[LOCALPLAYER] = PLAYING;
}
dx = ((Random() & 0x0003) - 2);
if (dx == 0)
{
dx = 2;
}
dy = ((Random() & 0x0003) - 2);
if (dy == 0)
{
dy = 2;
}
myXCoord += dx;
myYCoord += dy;
}
myVX *= 0.995;
myVY *= 0.995;
if (myVX > MAXSPEED)
myVX = MAXSPEED;
else if (myVX < -MAXSPEED)
myVX = -MAXSPEED;
if (myVY > MAXSPEED)
myVY = MAXSPEED;
else if (myVY < -MAXSPEED)
myVY = -MAXSPEED;
myXCoord += myVX*dt;
myYCoord += myVY*dt;
x = (short) myXCoord;
y = (short) myYCoord;
// did we bump the edge of the screen?
if (x < gameRect.left)
{
x = gameRect.left;
myXCoord = gameRect.left;
myVX = -myVX;
}
else if (x > gameRect.right)
{
x = gameRect.right;
myXCoord = gameRect.right;
myVX = -myVX;
}
if (y < gameRect.top)
{
y = gameRect.top;
myYCoord = gameRect.top;
myVY = -myVY;
}
else if (y > gameRect.bottom)
{
y = gameRect.bottom;
myYCoord = gameRect.bottom;
myVY = -myVY;
}
playerXCoord[LOCALPLAYER] = x;
playerYCoord[LOCALPLAYER] = y;
}
else
{
x = (short) myXCoord;
y = (short) myYCoord;
}
// figure out which (if any) keys have been pressed
GetKeys(theKeyMap);
if (theKeyMap[3] & LEFTARROWKEYMAPMASK)
{
playerAngleIndex[LOCALPLAYER] -= deltaTicks;
if (playerAngleIndex[LOCALPLAYER] <= MINANGLEINDEX)
{
playerAngleIndex[LOCALPLAYER] += MAXANGLEINDEX;
}
sendBroadcastUpdate = true;
}
if (theKeyMap[3] & RIGHTARROWKEYMAPMASK)
{
playerAngleIndex[LOCALPLAYER] += deltaTicks;
if (playerAngleIndex[LOCALPLAYER] >= MAXANGLEINDEX)
{
playerAngleIndex[LOCALPLAYER] -= MAXANGLEINDEX;
}
sendBroadcastUpdate = true;
}
// ahead half-impulse, Mr. Data....
if (theKeyMap[3] & UPARROWKEYMAPMASK)
{
myVX += vxTable[playerAngleIndex[LOCALPLAYER]];
myVY += vyTable[playerAngleIndex[LOCALPLAYER]];
}
// update photon positions no more than 60 times per second
if (deltaTicks > 0)
{
for (i=0;i<MAXNUMPHOTONS;i++)
{
if (photonTimeToLive[LOCALPLAYER][i] > 0)
{
photonTimeToLive[LOCALPLAYER][i] -= deltaTicks;
if (photonTimeToLive[LOCALPLAYER][i] > 0)
{
myPhotonXCoords[i] += myPhotonVXs[i]*dt;
myPhotonYCoords[i] += myPhotonVYs[i]*dt;
photonXCoord[LOCALPLAYER][i] = (short) myPhotonXCoords[i];
photonYCoord[LOCALPLAYER][i] = (short) myPhotonYCoords[i];
sendBroadcastUpdate = true;
}
}
}
}
// don't fire photons *too* fast or we'll burn out the emitter array, Mr. Worf
if ((TickCount() > photonLastTickCount + PHOTONFIREDELAY) && theKeyMap[1] & SPACEKEYMAPMASK)
{
if (photonTimeToLive[LOCALPLAYER][photonIndex] <= 0)
{
myPhotonXCoords[photonIndex] = myXCoord + xVertexTable[playerAngleIndex[LOCALPLAYER]][0];
myPhotonYCoords[photonIndex] = myYCoord + yVertexTable[playerAngleIndex[LOCALPLAYER]][0];
photonXCoord[LOCALPLAYER][photonIndex] = (short) myPhotonXCoords[photonIndex];
photonYCoord[LOCALPLAYER][photonIndex] = (short) myPhotonYCoords[photonIndex];
myPhotonVXs[photonIndex] = photonVXTable[playerAngleIndex[LOCALPLAYER]];
myPhotonVYs[photonIndex] = photonVYTable[playerAngleIndex[LOCALPLAYER]];
photonTimeToLive[LOCALPLAYER][photonIndex] = PHOTONLIFETIME;
SetPort((GrafPtr) gameWPtr);
PenNormal();
PenMode(patXor);
MoveTo((short) photonXCoord[LOCALPLAYER][photonIndex],(short) photonYCoord[LOCALPLAYER][photonIndex]);
Line(0,0);
photonIndex++;
if (photonIndex >= MAXNUMPHOTONS)
photonIndex = 0;
photonLastTickCount = TickCount();
PlayGameSound(gameMyPhotonSNDHandle);
}
}
// redraw everyone offscreen
for (i=0;i<currentNumPlayers;i++)
{
DrawOffscreenShip(i);
}
// erase the photons' old positions
ErasePhotons();
// show everyone onscreen
for (i=0;i<currentNumPlayers;i++)
{
ShowShip(i);
}
// redraw photons at their new positions
DrawPhotons();
// should we send a new update of our position?
if (sendBroadcastUpdate == true && dataBroadcastIsComplete == true)
{
sendBroadcastUpdate = false;
dataBroadcastIsComplete == false;
BroadcastLocalPlayerData();
}
// did we just get hit by another player's photon?
if (startExplosionSND == true)
{
startExplosionSND = false;
errCode = SndChannelStatus(gameExplosionSndChannelPtr,sizeof(theStatus),&theStatus);
if (errCode == noErr && theStatus.scChannelBusy == false)
{
errCode = SndPlay(gameExplosionSndChannelPtr,(SndListHandle) gameExplosionSNDHandle,true);
}
}
}